#include "../config.h"

#include "format.h"
#include "conf.h"
#include "strfuncs.h"

namespace gpyp {

//========================
// VariableName
bool VariableName::match(const Glib::ustring &n, bool end) {
  bool res = (counter_ < name_.size() &&
              end == (name_.size() == (counter_ + 1)) &&
              name_[counter_] == n);
  if (!end && res) push();
  return res;
}
void VariableName::push(size_t count) {
  counter_ += count;
}
void VariableName::reset() {
  counter_ = 0;
}

VariableName::operator bool() const {
  return counter_ < name_.size();
}
bool VariableName::operator!() const {
  return counter_ >= name_.size();
}
bool VariableName::get(const Glib::ustring *&n1,
                       bool end) const {
  if (counter_ < name_.size() &&
      (!end || name_.size() == (counter_ + 1))) {
    n1 = &name_[counter_];
    return true;
  }
  return false;
}
bool VariableName::get(const Glib::ustring *&n1,
                       const Glib::ustring *&n2,
                       bool end) const {
  if ((counter_ + 1) < name_.size() &&
      (!end || name_.size() == (counter_ + 2))) {
    n1 = &name_[counter_];
    n2 = &name_[counter_ + 1];
    return true;
  }
  return false;
}

const std::vector<Glib::ustring> &VariableName::get_name() const {
  return name_;
}
bool VariableName::get_arg(size_t idx, Glib::ustring &arg) const {
  if (idx < args_.size()) {
    arg = args_[idx];
    return true;
  }
  return false;
}

size_t VariableName::get_counter() const {
  return counter_;
}

bool VariableName::validate(const Glib::ustring &str, char prefix) {
  char head[3] = {prefix, '{', '\0'};
  return (str.length() > 3 &&
          str.compare(0, 2, head) == 0 &&
          str.compare(str.length() - 1, 1, "}") == 0);
}

VariableName::VariableName(const Glib::ustring &name,
                           size_t offset_s,
                           size_t offset_e) :
  counter_(0)
{
  if (name.bytes() > offset_s + offset_e) {
    bool inname = true;
    const char* l = name.c_str() + offset_s;
    const char* e = name.c_str() + name.bytes() - offset_e;
    for(const char* c = l; c < e; ++c) {
      switch(*c) {
      case '.':
        if (inname) {
          name_.push_back(Glib::ustring(l, c));
          l = c + 1;
        }
        break;
      case '|':
        if (inname) {
          name_.push_back(Glib::ustring(l, c));
          l = c + 1;
          inname = false;
        } else {
          args_.push_back(Glib::ustring(l, c));
          l = c + 1;
        }
      }
    }
    if (inname) {
      name_.push_back(Glib::ustring(l, e));
    } else {
      args_.push_back(Glib::ustring(l, e));
    }
  }
}

//========================
// FormatTermBase
void FormatTermBase::set_data(const Glib::ustring data, bool html) {
  if (html) {
    encode_html(data, data_);
  } else {
    data_ = data;
  }
}

void FormatTermBase::prepare(RefChannel/* ch*/, bool/* html*/) {
}
size_t FormatTermBase::calc_size() {
  return delimiter_.bytes() + data_.bytes();
}
bool FormatTermBase::empty() {
  return data_.empty();
}
bool FormatTermBase::format(Glib::ustring &str, bool setdelimiter) {
  if (!data_.empty()) {
    if (setdelimiter) {
      str.append(delimiter_);
    }
    str.append(data_);
    return true;
  }
  return setdelimiter && !delimiter_.empty();
}
int FormatTermBase::compare(RefChannel/* ch1*/, RefChannel/* ch2*/) {
  return 0;
}

FormatTermBase::FormatTermBase(const Glib::ustring &delimiter) :
  delimiter_(delimiter)
{
}
FormatTermBase::~FormatTermBase() {
}

//========================
// FormatTermProperty
void FormatTermProperty::prepare(RefChannel ch, bool html) {
  if (ch) {
    set_data((ch.get()->*(prop_.func_str))(), html);
  } else {
    data_.clear();
  }
}
int FormatTermProperty::compare(RefChannel ch1, RefChannel ch2) {
  if (prop_.func_int) {
    return (ch1.get()->*(prop_.func_int))() - (ch2.get()->*(prop_.func_int))();
  } else if (prop_.func_str) {
    const Glib::ustring &str1 = (ch1.get()->*(prop_.func_str))();
    const Glib::ustring &str2 = (ch2.get()->*(prop_.func_str))();
    return str1.compare(str2);
  }
  return 0;
}

FormatTermProperty::FormatTermProperty(const Glib::ustring &delimiter, const Channel::Property &prop) :
  FormatTermBase(delimiter),
  prop_(prop)
{
}

//========================
// FormatTermList
void FormatTermList::add_term(Glib::ustring &delimiter, const Channel::Property &prop) {
  terms_.push_back(RefFormatTerm(new FormatTermProperty(delimiter, prop)));
  delimiter.clear();
}

void FormatTermList::prepare(RefChannel ch, bool html) {
  for(RefFormatTermList::iterator iter = terms_.begin(); iter != terms_.end(); ++iter) {
    (*iter)->prepare(ch, html);
  }
}
size_t FormatTermList::calc_size() {
  size_t s = FormatTermBase::calc_size() + 2 + prefix_.bytes() + suffix_.bytes();
  for(RefFormatTermList::iterator iter = terms_.begin(); iter != terms_.end(); ++iter) {
    s += (*iter)->calc_size();
  }
  return s;
}
bool FormatTermList::empty() {
  for(RefFormatTermList::iterator iter = terms_.begin(); iter != terms_.end(); ++iter) {
    if (!(*iter)->empty()) {
      return false;
    }
  }
  return true;
}
bool FormatTermList::format(Glib::ustring &str, bool setdelimiter) {
  if (str.empty()) {
    size_t len = calc_size();
    if (str.capacity() < len) {
      str.reserve(len);
    }
  }
  bool empty_ = empty();
  if (!empty_ || !prefix_.empty() || !suffix_.empty() || bracket_s_ == '\n') {
    if (setdelimiter || bracket_s_ == '\n') {
      str.append(delimiter_);
    }
    if (bracket_s_) {
      str += bracket_s_;
    }
    str.append(prefix_);

    bool setdelimiter_ = false;
    for(RefFormatTermList::iterator iter = terms_.begin(); iter != terms_.end(); ++iter) {
      setdelimiter_ = (*iter)->format(str, setdelimiter_);
    }

    str.append(suffix_);
    if (bracket_e_) {
      str += bracket_e_;
    }
    return true;
  }
  return !empty_ || (setdelimiter && !delimiter_.empty());
}
int FormatTermList::compare(RefChannel ch1, RefChannel ch2) {
  if (ch1 && ch2) {
    int res = 0;
    for(RefFormatTermList::iterator iter = terms_.begin(); iter != terms_.end(); ++iter) {
      if ((res = (*iter)->compare(ch1, ch2)) != 0) {
        return res;
      }
    }
  }
  return 0;
}

bool FormatTermList::format(Glib::ustring &str, RefChannel ch, bool html) {
  prepare(ch, html);
  return format(str, false);
}

void FormatTermList::parse(const Glib::ustring &format) {
  terms_.clear();
  bool escaped = false;
  Glib::ustring str;
  char br_s, br_e = '\0';
  const char *l = format.c_str();
  for(const char *c = l; *c; ++c) {
#define PRE_ADD_TERM(end) {                     \
      if (terms_.empty()) {                     \
        prefix_.swap(str);                      \
        prefix_.append(l, c);                   \
        str.clear();                            \
      } else {                                  \
        str.append(l, c);                       \
      }                                         \
      l = (c = (end)) + 1;                      \
    }

    if (escaped) {
      escaped = false;
      continue;
    }
    switch(*c) {
    case '\\':
      escaped = true;
      str.append(l, c);
      l = c + 1;
      break;
    case '%':
      if (c[1] == '{') {
        const char *key_e = c + 2;
        for( ; *key_e && *key_e != '}'; ++key_e) ;

        Glib::ustring key(c + 2, key_e);
        VariableName var(key);

        if (var.match("YP", false)) {
          if (var.match("chcount")) {
            PRE_ADD_TERM(key_e);
            add_term<size_t>(str, sigc::mem_fun(*ypcollection_, &YPCollection::get_chcount));
          } else if (var.match("chcount_valid")) {
            PRE_ADD_TERM(key_e);
            add_term<size_t>(str, sigc::mem_fun(*ypcollection_, &YPCollection::get_chcount_valid));
          }
        } else if (var.match("Channel", false)) {
          if (var.match("longdesc")) {
            PRE_ADD_TERM(key_e);
            // [%{Channel.genre}]%{Channel.desc} - %{Channel.comment} - %{Channel.track_artist} - %{Channel.track_title} - %{Channel.track_album}
            terms_.push_back(RefFormatTerm(new FormatTermList(conf_, ypcollection_,
                                                              "[%{Channel.genre}]%{Channel.desc} - "
                                                              "%{Channel.comment} - %{Channel.track_artist} - "
                                                              "%{Channel.track_title} - %{Channel.track_album}",
                                                              str)));
            str.clear();
          } else {
            const Glib::ustring *name = NULL;
            if (var.get(name)) {
              const Channel::Property *prop =
                Channel::get_property(name->c_str());
              if (prop) {
                PRE_ADD_TERM(key_e);
                add_term(str, *prop);
              }
            }
          }
        }
      }
      break;
    case '[':
      br_e = ']';
    case '(':
      if (!br_e) br_e = ')';
    case '<':
      if (!br_e) br_e = '>';
    case '\n':
      if (!br_e) br_e = '\n';

      br_s = *c;
      {
        const char *format_e = c + 1;
        for( ; *format_e && *format_e != br_e; ++format_e) ;
        Glib::ustring format_(c + 1, format_e);

        if (br_e == '\n') {
          --format_e;
          br_e = '\0';
        }
        if (br_s == '(') {
          br_s = br_e = '\0';
        }
        PRE_ADD_TERM(format_e);
        terms_.push_back(RefFormatTerm(new FormatTermList(conf_, ypcollection_,
                                                          format_, str, br_s, br_e)));
        str.clear();
      }

      br_s = br_e = '\0';
      break;
    }
#undef PRE_ADD_TERM
  }
  suffix_.assign(l);
}

FormatTermList::FormatTermList(Conf &conf, RefYPCollection ypcollection,
                               const Glib::ustring &format,
                               const Glib::ustring &delimiter,
                               char bracket_s, char bracket_e) :
  FormatTermBase(delimiter),
  conf_(conf),
  ypcollection_(ypcollection),
  bracket_s_(bracket_s),
  bracket_e_(bracket_e)
{
  parse(format);
}

}/*gpyp*/
